package org.zhangyp.springbootsecurity.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.zhangyp.springbootsecurity.authentication.PhoneCodeAuthenticationProvider;
import org.zhangyp.springbootsecurity.result.DemoAccessDeniedHandler;
import org.zhangyp.springbootsecurity.result.DemoAuthenticationEntryPoint;
import org.zhangyp.springbootsecurity.service.JdbcUserDetailsServiceImpl;

/**
 * Security 相关配置
 *
 * @author ypzhang
 * @since 2025/6/6
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private JdbcUserDetailsServiceImpl jdbcUserDetailsService;

    /*@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeRequests(authorize ->
                        authorize
                                .requestMatchers("/index.html", "/css/**", "/js/**", "/img/**", "/favicon.ico").permitAll() //登录页和静态资源可以直接通过
                                .requestMatchers("/mobile/**").hasAuthority("mobile") //配置资源权限
                                .requestMatchers("/salary/**").hasAuthority("salary")
                                .requestMatchers("/common/**").permitAll() //common下的请求直接通过
                                .anyRequest().authenticated()
                )
                .rememberMe(config -> config.rememberMeCookieName("remember-me"))
                .formLogin(loginConfigurer ->
                        loginConfigurer
                                .loginPage("/index.html").loginProcessingUrl("/login")
                                .defaultSuccessUrl("/main.html")
                                .failureUrl("/common/loginFailed")
                )
                //.httpBasic(withDefaults())  //开启httpBasic认证，开启后会弹个框输入用户名密码
                ;
        return http.build();
    }*/

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 所有访问都需要认证
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login", "phoneCodeLogin", "/css/**", "/js/**", "/img/**", "/favicon.ico").permitAll()
                        .anyRequest().authenticated()
                )
                // 配置异常处理
                .exceptionHandling(handling -> handling
                        // 认证异常处理 -- 测试授权异常时，可以注释掉DemoAuthenticationEntryPoint
                        .authenticationEntryPoint(new DemoAuthenticationEntryPoint())
                        // 授权异常处理
                        .accessDeniedHandler(new DemoAccessDeniedHandler()))
                // 禁用csrf，因为登录和登出是post请求，csrf会屏蔽掉post请求
                .csrf(AbstractHttpConfigurer::disable)
                // 添加到过滤器链路中，确保在AuthorizationFilter过滤器之前
                .addFilterBefore(jwtAuthenticationTokenFilter, AuthorizationFilter.class)
                // 由于采用token方式认证，因此可以关闭session管理
                .sessionManagement(AbstractHttpConfigurer::disable)
                // 自定义登录界面，并允许无需认证即可访问
                .formLogin(AbstractHttpConfigurer::disable)
                // 关闭默认的登出
                .logout(AbstractHttpConfigurer::disable);
        return http.build();
    }

    /**
     * 自行注入一个PasswordEncoder。
     *
     * @return
     */
    @Bean
    public PasswordEncoder getPassWordEncoder() {
        return new BCryptPasswordEncoder(10);
        //return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 默认密码认证的 Provider
     */
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        // 保留原来账号密码登录的AuthenticationProvider
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(jdbcUserDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(getPassWordEncoder());
        return daoAuthenticationProvider;
    }

    /**
     * 定义手机验证码需要的phoneCodeAuthenticationProvider
     */
    @Bean
    public PhoneCodeAuthenticationProvider phoneCodeAuthenticationProvider() {
        return new PhoneCodeAuthenticationProvider();
    }

    /**
     * 定义AuthenticationManager，加入两种AuthenticationProvider
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        // 将daoAuthenticationProvider和 phoneCodeAuthenticationProvider 加入到authenticationManager
        return new ProviderManager(daoAuthenticationProvider(), phoneCodeAuthenticationProvider());
    }

    /**
     * 自行注入一个UserDetailsService
     * 如果没有的话，在UserDetailsServiceAutoConfiguration中会默认注入一个包含user用户的InMemoryUserDetailsManager
     * 另外也可以采用修改configure(AuthenticationManagerBuilder auth)方法并注入authenticationManagerBean的方式。
     *
     * @return
     */
    /*@Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user").password(getPassWordEncoder().encode("password")).roles("USER").build());
        manager.createUser(User.withUsername("admin").password(getPassWordEncoder().encode("password")).roles("USER", "ADMIN").build());
        return manager;
    }*/
    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(10);
        String s = bCryptPasswordEncoder.encode("1234");
        System.out.println(s);
    }
}
